﻿HmJS.$Import('core.fx');

/*
---
@fileoverview 文档支持 - 右侧菜单悬浮效果，以及锚点滚动效果

How to use
----------
	_win.addEvent('domready', function () {

		var SmoothScroll = new Fx.SmoothScroll();
		var _rightfloat = new clsAreaFloat($$('.doc .methods')[0]);

		//绑定事件
		var activefloat = _rightfloat.active.bind(_rightfloat);
		_win.addEvents({
			'scroll': activefloat,
			'resize': activefloat,
			'beforeunload': function () {
				this.removeEvents();
			}
		});
		_rightfloat.active();
	});
...
*/

HmJS.register('util.dom.AreaFloat', function ($ns) {

	var _win = window,
			_doc = document;

	/**
	* 初始化一个浮动对象，使其在一定区域内随屏幕滚动浮动显示（尽可能在屏幕区域内显示）。
	* @author	梁栋[pillar0514@gmail.com]
	* @param {HTMLElement} el 操作的目标元素
	* @param {HTMLElement} options 选项
	* @example
	var _rightfloat = new clsAreaFloat($$('.doc .methods')[0]);
	var activefloat = _rightfloat.active.bind(_rightfloat);
	_win.addEvents({
	'scroll':activefloat,
	'resize':activefloat
	});
	_rightfloat.active();
	*
	*/
	return new Class({

		Implements: [Options],

		options: {
			target: null, 					//浮动目标对象
			area: null, 						//浮动对象的区域参照对象
			duration: 300, 					//动画延时
			timeout: 2000, 					//心跳检测延时
			checkonce: { target: false, area: false }, //只检查一次，如果浮动元素或者容器元素的高度都是不变的，可以启用对应的值
			targetStyle: { 'margin-top': 'top' }, //要操作的浮动对象的样式 {'对应的CSS值':'相对值','对应的CSS值':'相对值'}，可用相对值：['top','bottom','left','right']
			margin: [0, 0, 0, 0]					//浮动对象与区域参照对象的边距 [上，右，下，左]
		},

		//初始化
		initialize: function (el, options) {
			this.setOptions(options);
			var ops = this.options;
			this.pos = {};
			this.target = $(el) || $(ops.target);
			if (this.target) {
				this.area = $(ops.area) || this.target.getParent(); //默认取父元素为区域参照对象
			}
			if (this.target && this.area) {
				this.enable = true;
			} else {
				return;
			}
			this.fx = new Fx.Morph(this.target, { fps: 50, duration: ops.duration });
		},

		//立即检查各项数据，修正目标的位置
		check: function () {
			if (!this.enable) { return; }
			this.compute();
			Object.each(this.options.targetStyle, function (prop, key) {
				this.target.setStyle(key, this.pos[prop]);
			}, this);
		},

		//心跳检测 - 每调用一次此函数可以让动画定时器存在时间延长一段时间。
		active: function () {
			if (!this.enable) { return; }
			this.animate();
			var ops = this.options,
					atime = ops.timeout > ops.duration * 2 ? ops.timeout : ops.duration * 2;
			if (this.atimer) {
				clearTimeout(this.atimer);
				delete this.atimer;
			}
			this.atimer = setTimeout(function () {
				clearInterval(this.timer);
				delete this.timer;
			} .bind(this), atime);
		},

		//加载动画定时器。
		animate: function () {
			if (!this.enable) { return; }
			if (!this.timer) {
				this._animatepos();
				this.timer = setInterval(this._animatepos.bind(this), this.options.duration);
			}
		},

		//计算目标当前应该所在的相对位置
		compute: function () {
			if (!this.enable) { return; }
			this.sinfo = this._getScreenInfo();
			this.tinfo = this._getElementInfo(this.target);
			this.ainfo = this._getElementInfo(this.area);
			this._computepos('width', 'left', 'right', 3, 1, this.sinfo, this.tinfo, this.ainfo); 	//检查横向
			this._computepos('height', 'top', 'bottom', 0, 2, this.sinfo, this.tinfo, this.ainfo); 	//检查纵向
		},

		//通过动画修正目标的位置
		_animatepos: function () {
			this.compute();
			var pos = this.pos,
					tinfo = this.tinfo,
					ainfo = this.ainfo;
			var nowpos = {
				'top': tinfo.top - ainfo.top,
				'left': tinfo.left - ainfo.left
			};
			nowpos.bottom = tinfo.height + nowpos.top;
			nowpos.right = tinfo.width + nowpos.left;
			var fxpara = {};
			Object.each(this.options.targetStyle, function (prop, key) {
				fxpara[key] = [nowpos[prop], pos[prop]];
			}, this);
			this.fx.cancel().start(fxpara);
		},

		/**
		* 计算横纵边距 - 核心算法
		* @param {string} prop	 对应元素大小相关属性 	['wdith','height']
		* @param {string} d1	 对应元素位置相关属性 	['left','top']	
		* @param {string} d2	 对应元素位置相关属性 	['right','bottom']	
		* @param {number} n1	 对应margin数组下标	 	[3,0] (左，上)
		* @param {number} n2	 对应margin数组下标	 	[1,2] (右，下)
		* @param {object} sinfo 窗口大小与位置信息
		* @param {object} tinfo 目标元素大小与位置信息
		* @param {object} ainfo 区域参照元素的大小与位置信息
		*/
		_computepos: function (prop, d1, d2, n1, n2, sinfo, tinfo, ainfo) {
			var margin = this.options.margin,
					pos = {},
					min = margin[n1],
					max = ainfo[prop] - tinfo[prop] - margin[n2],
					pre = this['pre' + d1] || min;
			if (tinfo[prop] + margin[n2] + margin[n1] < ainfo[prop]) {									//以纵向为例，如果目标元素高度小于区域元素高度，才执行计算
				pos[d1] = (sinfo[prop] > tinfo[prop] || sinfo[d1] < tinfo[d1]) ? 					//如果窗口高度大于目标元素高度，或者窗口上限坐标小于目标元素上限坐标
							sinfo[d1] - ainfo[d1] : 													//则目标元素上边距应当为（窗口上限坐标 - 区域上限坐标）
							(sinfo[d2] > tinfo[d2] ? sinfo[d2] - tinfo[prop] - ainfo[d1] : pre);     //否则检查窗口下限与目标元素下限，如果超出，则目标元素上边距为 （窗口下限 - 区域上限 - 目标元素高度）
				pos[d1] = pos[d1].limit(min, max); 												//限制计算出的边距值在最大值和最小值之间。
			} else {																						//否则目标元素高度为最小值
				pos[d1] = min;
			}
			this['pre' + d1] = pos[d1];
			this.pos[d1] = pos[d1];
			this.pos[d2] = ainfo[prop] - tinfo[prop] - pos[d1];
		},

		//获取元素相对于页面的位置偏移，与大小
		_getElementInfo: function (el) {
			var _xy = el.getPosition(),
					_size = el.getSize();
			return {
				left: _xy.x,
				right: _xy.x + _size.x,
				top: _xy.y,
				bottom: _xy.y + _size.y,
				width: _size.x,
				height: _size.y
			};
		},

		//获取窗口相对于页面的位置
		_getScreenInfo: function () {
			var _wscroll = _win.getScroll(),
					_wsize = _win.getSize();
			return {
				top: _wscroll.y,
				bottom: _wscroll.y + _wsize.y,
				left: _wscroll.x,
				right: _wscroll.x + _wsize.x,
				width: _wsize.x,
				height: _wsize.y
			};
		}

	});

});